#include <stdlib.h>
#include <string.h>

#include <common/macro/array.h>
#include <common/macro/debug.h>

#include <storage/page.h>
#include <storage/blob/page_blob.h>
/*#include <storage/index/page_index.h>*/
#include <storage/tuple/page_tuple.h>

API_LIBLOCAL page_t *page_new(page_type_e type, page_size_t page_size) {
	page_t *page;

	page = malloc(sizeof(*page));
	page->type = type;
	page->size = page_size;
	page->raw = NULL;

	/* Ensure free space is correct to start with. */
	page->freespace = page->size;
	page->freespace -= sizeof(char);
	page->freespace -= sizeof(page->freespace);
	/* Okay, looks good. */

	switch (page->type) {
		case PAGE_BLOB:
			page->blob = page_blob_new(page);
			if (page->blob == NULL) {
				page_destroy(page);
				return NULL;
			}
			break;
/*		case PAGE_INDEX:
			page->index = page_index_new(page);
			if (page->index == NULL) {
				page_destroy(page);
				return NULL;
			}
			break;*/
		case PAGE_TUPLE:
			page->tuple = page_tuple_new(page);
			if (page->tuple == NULL) {
				page_destroy(page);
				return NULL;
			}
			break;

		default:
			break;
	}

	return page;
}

API_LIBLOCAL inline char page_type_to_char(page_type_e type) {
	switch (type) {
		case PAGE_ANY:
			return '?';

		case PAGE_LEASE:
			return '!';

		case PAGE_TUPLE:
			return 'T';

		case PAGE_BLOB:
			return 'B';

		case PAGE_INDEX:
			return 'I';
	}

	return '\0';
}

API_LIBLOCAL inline page_type_e page_char_to_type(char c) {
	switch (c) {
		case 'T':
			return PAGE_TUPLE;

		case 'B':
			return PAGE_BLOB;

		case 'I':
			return PAGE_INDEX;

		case '!':
			return PAGE_LEASE;

		default:
			return PAGE_ANY;
	}
}

API_LIBLOCAL int page_serialize(page_t *page, char *out, ssize_t out_size) {
	char page_type_char;
	ssize_t offset, itemlen;

	assert(page != NULL);
	assert(out != NULL);
	assert(out_size >= page->size);

	offset = 0;

	/* Page type. */
	page_type_char = page_type_to_char(page->type);
	itemlen = sizeof(page_type_char);
	if (itemlen > out_size - offset) { return -1; }
	memcpy(&out[offset], &page_type_char, itemlen);
	offset += itemlen;

	/* Free space. */
	itemlen = sizeof(page->freespace);
	if (itemlen > out_size - offset) { return -1; }
	memcpy(&out[offset], &page->freespace, itemlen);
	offset += itemlen;

	switch (page->type) {
		case PAGE_BLOB:
			return page_blob_serialize(page->blob, &out[offset], out_size - offset);

/*		case PAGE_INDEX:
			return page_index_serialize(page, &out[offset], out_size - offset);*/

		case PAGE_TUPLE:
			debugmsg("Data Offset: %u.\n", offset);
			return page_tuple_serialize(page->tuple, &out[offset], out_size - offset);

		case PAGE_ANY:
			return 0;

		default:
			break;
	}

	return -1;
}

API_LIBLOCAL int page_deserialize(page_t **page, char *in, ssize_t in_size) {
	char page_type_char;
	ssize_t offset, itemlen;

	assert(page != NULL);
	assert(in != NULL);

	offset = 0;

	/* Page type. */
	itemlen = sizeof(page_type_char);
	if (itemlen > in_size - offset) { return -1; }
	memcpy(&page_type_char, &in[offset], itemlen);
	offset += itemlen;

	/* We assume that in_size == page size. */
	if (*page == NULL) {
		*page = malloc(sizeof(**page));
	}
	(*page)->type = page_char_to_type(page_type_char);
	(*page)->size = in_size;
	(*page)->raw = in;

	/* Free space. */
	itemlen = sizeof((*page)->freespace);
	if (itemlen > in_size - offset) { return -1; }
	memcpy(&(*page)->freespace, &in[offset], itemlen);
	offset += itemlen;

	switch ((*page)->type) {
		case PAGE_BLOB:
			return page_blob_deserialize(&(*page)->blob, &in[offset], in_size - offset, *page);

/*		case PAGE_INDEX:
			return page_index_deserialize(&page->index, &out[offset], out_size - offset, *page);*/

		case PAGE_TUPLE:
			debugmsg("Data Offset: %u.\n", offset);
			return page_tuple_deserialize(&(*page)->tuple, &in[offset], in_size - offset, *page);

		case PAGE_ANY:
			return 0;

		default:
			break;
	}

	return -1;
}

API_LIBLOCAL inline void page_set_freespace(page_t *page, page_size_t freespace) {
	assert(page != NULL);
	page->freespace = freespace;
}

API_LIBLOCAL inline void page_decrease_freespace(page_t *page, page_size_t freespace) {
	assert(page != NULL);
	page->freespace -= freespace;
}

API_LIBLOCAL inline void page_increase_freespace(page_t *page, page_size_t freespace) {
	assert(page != NULL);
	page->freespace += freespace;
}

API_LIBLOCAL inline page_size_t page_get_freespace(page_t *page) {
	assert(page != NULL);
	return page->freespace;
}

API_LIBLOCAL void page_set_empty(page_t *page) {
	switch (page->type) {
		case PAGE_BLOB: page_blob_destroy(page->blob); break;
/*		case PAGE_INDEX: page_index_destroy(page->index); break;*/
		case PAGE_TUPLE: page_tuple_destroy(page->tuple); break;
		default: break;
	}
	page->type = PAGE_ANY;

	page->freespace = page->size;
	page->freespace -= sizeof(char);
	page->freespace -= sizeof(page->freespace);
}

API_LIBLOCAL void page_destroy(page_t *page) {
	if (page == NULL) return;

	free(page->raw);
	switch (page->type) {
		case PAGE_BLOB: page_blob_destroy(page->blob); break;
/*		case PAGE_INDEX: page_index_destroy(page->index); break;*/
		case PAGE_TUPLE: page_tuple_destroy(page->tuple); break;
		default: break;
	}
	free(page);
}
